﻿//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

using ProjectBase.Core;

using $prjsn$.Entity;
using $prjsn$.Common;


namespace $safeprojectname$
{
    public class AdditionalBinder<TFilter> : IService, IMessagesHolder, IPublishingReporter
        where TFilter : BusinessObjectFilter, new()
    {
        public TFilter BindAdditional(TFilter filter, IValueContainer valueContainer, MetaDataMessage metaData, IObjectBinder binder)
        {
            var candBind = filter.Is()
                           && valueContainer.ContainsPrefix("Additional");
            if (!candBind)
            {
                return filter;
            }

            var meta = metaData
                .Attributes
                .SingleOrDefault(md => md.IsAdditional);

            foreach (var attribute in meta.Attributes)
            {
                var wasBounded = BindAdditionalString(filter, valueContainer, attribute)
                    || BindAdditionalValueTypes<int>(filter, valueContainer, attribute)
                    || BindAdditionalValueTypes<decimal>(filter, valueContainer, attribute)
                    || BindAdditionalValueTypes<DateTime>(filter, valueContainer, attribute)
                    || BindAdditionalValueTypes<bool>(filter, valueContainer, attribute);

                if (!wasBounded)
                {
                    BindPersistentFilter(filter, valueContainer, attribute, binder);
                }

            }

            return filter;
        }

        protected virtual bool BindPersistentFilter(TFilter filter, IValueContainer valueContainer, MDAttribute attribute, IObjectBinder binder)
        {
            if (!attribute.IsEntityReference)
            {
                return false;
            }



            // Try to create the Filter based on the MetaData of the IPersistent
            var name = attribute.Name;
            var entityName = name;
            if (attribute.HasSubtype)
            {
                entityName = attribute.Type.Name;
            }
            var typeName = attribute.Type.AssemblyQualifiedName;
            var filterTypeName = typeName.Replace(entityName + ",", entityName + "Filter,");
            var filterType = Type.GetType(filterTypeName);

            if (filterType.IsNull())
            {
                return false;
            }

            // Bind that filter
            var innerFilter = Activator.CreateInstance(filterType);
            binder.Bind(filterType, innerFilter, valueContainer, "Additional." + name);

            // pass it as a part of the 

            filter.Additional[name] = innerFilter;

            return true;
        }

        protected virtual bool BindAdditionalString(TFilter filter
            , IValueContainer valueContainer, MDAttribute attribute)
        {
            var isString = attribute.Type.IsEquivalentTo(typeof(string));

            if (!isString)
            {
                return false;
            }

            var key = "Additional." + attribute.Name;
            var value = valueContainer.GetValue(key);

            if (value.Is())
            {
                filter.Additional[key] = value.RawValue;
            }
            return true;
        }

        protected virtual bool BindAdditionalValueTypes<TValue>(TFilter filter
            , IValueContainer valueContainer, MDAttribute attribute)
            where TValue : struct
        {
            var isCompareable = attribute.Type.IsEquivalentTo(typeof(TValue))
                                || attribute.Type.IsEquivalentTo(typeof(TValue?))
                ;

            TValue value = default(TValue);

            if (!isCompareable)
            {
                return false;
            }

            IValueResult valueResult;
            var key = "Additional." + attribute.Name;

            // more (in clause) Salary eq 1000 or Salary eq null
            if (TryGetRawValue(valueContainer, key, out valueResult))
            {
                var rawValues = valueResult.RawValue as IEnumerable<string> ?? new[] { valueResult.AttemptedValue };
                var list = new List<TValue?>();

                foreach (var item in rawValues)
                {
                    if (item.IsEqual("null"))
                    {
                        list.Add(null);
                    }
                    if (item.TryParse(out value))
                    {
                        list.Add(value);
                    }
                    else
                    {
                        this.PublishError(Str.Messages.BindingTypeError3
                        , "Additional." + attribute.Name
                        , attribute.Type.Name
                        , item);
                    }
                }

                filter.Additional[key] = list;
                return true;
            }

            // greater or equal
            var keyFrom = key + "From";
            if (TryGetValue(valueContainer, keyFrom, out value))
            {
                filter.Additional[key + "[From]"] = value;
                return true;
            }

            // less or equal
            var keyTo = key + "To";
            if (TryGetValue(valueContainer, keyTo, out value))
            {
                filter.Additional[key + "[To]"] = value;
                return true;
            }

            return true;
        }

        protected virtual bool TryGetValue<TValue>(IValueContainer valueContainer, string name, out TValue value)
            where TValue : struct
        {
            value = default(TValue);

            IValueResult rawValue;

            if (!TryGetRawValue(valueContainer, name, out rawValue))
            {
                return false;
            }

            return rawValue.AttemptedValue.TryParse(out value, CultureInfo.InvariantCulture);
        }

        protected virtual bool TryGetRawValue(IValueContainer valueContainer, string name, out IValueResult value)
        {
            value = null;
            var rawValue = valueContainer.GetValue(name);

            var notFound = rawValue.IsNull()
                       || rawValue.AttemptedValue.IsEmpty();

            if (notFound)
            {
                return false;
            }

            value = rawValue;
            return true;
        }

        public Messages Messages { get; set; }
    }
}
